//
//  CWStatusBarNotification.m
//  CWNotificationDemo
//
//  Created by Cezary Wojcik on 11/15/13.
//  Copyright (c) 2015 Cezary Wojcik. All rights reserved.
//

#import "CWStatusBarNotification.h"
#import <QuartzCore/CoreAnimation.h>

#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)

#define FONT_SIZE    12.0f
#define PADDING      10.0f
#define SCROLL_SPEED 40.0f
#define SCROLL_DELAY 1.0f

# pragma mark - ScrollLabel

@implementation ScrollLabel
{
    UIImageView *textImage;
}

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        textImage = [[UIImageView alloc] init];
        [self addSubview:textImage];
    }
    return self;
}

- (CGFloat)fullWidth
{
    return [self.text sizeWithAttributes:@{ NSFontAttributeName: self.font }].width;
}

- (CGFloat)scrollOffset
{
    if (self.numberOfLines != 1) return 0;

    CGRect insetRect = CGRectInset(self.bounds, PADDING, 0);
    return MAX(0, [self fullWidth] - insetRect.size.width);
}

- (CGFloat)scrollTime
{
    return ([self scrollOffset] > 0) ? [self scrollOffset] / SCROLL_SPEED + SCROLL_DELAY : 0;
}

- (void)drawTextInRect:(CGRect)rect
{
    if ([self scrollOffset] > 0) {
        rect.size.width = [self fullWidth] + PADDING * 2;
        UIGraphicsBeginImageContextWithOptions(rect.size, NO, [UIScreen mainScreen].scale);
        [super drawTextInRect:rect];
        textImage.image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        [textImage sizeToFit];
        [UIView animateWithDuration:[self scrollTime] - SCROLL_DELAY
                              delay:SCROLL_DELAY
                            options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveEaseInOut
                         animations:^{
            textImage.transform = CGAffineTransformMakeTranslation(-[self scrollOffset], 0);
        } completion:^(BOOL finished) {
        }];
    } else {
        textImage.image = nil;
        [super drawTextInRect:CGRectInset(rect, PADDING, 0)];
    }
}

@end

# pragma mark - CWWindowContainer

@implementation CWWindowContainer

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    CGFloat height;
    if (SYSTEM_VERSION_LESS_THAN(@"8.0") && UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
        height = [UIApplication sharedApplication].statusBarFrame.size.width;
    } else {
        height = [UIApplication sharedApplication].statusBarFrame.size.height;
    }
    if (point.y > 0 && point.y < (self.notificationHeight != 0.0 ? self.notificationHeight : height)) {
        return [super hitTest:point withEvent:event];
    }

    return nil;
}

@end

# pragma mark - CWViewController

@interface CWViewController ()
{
    UIInterfaceOrientationMask _cwSupportedInterfaceOrientation;
    UIStatusBarStyle _cwPreferredStatusBarStyle;
}

@end

@implementation CWViewController

- (void)updateSupportedInterfaceOrientations:(UIInterfaceOrientationMask)supportedOrientations
{
    _cwSupportedInterfaceOrientation = supportedOrientations;
}

- (void)updateStatusBarStyle:(UIStatusBarStyle)style
{
    _cwPreferredStatusBarStyle = style;
}

- (UIStatusBarStyle)preferredStatusBarStyle
{
    return _cwPreferredStatusBarStyle;
}

- (UIInterfaceOrientationMask)supportedInterfaceOrientations
{
    return _cwSupportedInterfaceOrientation;
}

- (BOOL)prefersStatusBarHidden
{
    return YES;
}

@end

# pragma mark - dispatch after with cancellation
// adapted from: https://github.com/Spaceman-Labs/Dispatch-Cancel

typedef void (^CWDelayedBlockHandle)(BOOL cancel);

static CWDelayedBlockHandle perform_block_after_delay(CGFloat seconds, dispatch_block_t block)
{
    if (block == nil) {
        return nil;
    }

    __block dispatch_block_t blockToExecute = [block copy];
    __block CWDelayedBlockHandle delayHandleCopy = nil;

    CWDelayedBlockHandle delayHandle = ^(BOOL cancel) {
        if (NO == cancel && nil != blockToExecute) {
            dispatch_async(dispatch_get_main_queue(), blockToExecute);
        }

        blockToExecute = nil;
        delayHandleCopy = nil;
    };

    delayHandleCopy = [delayHandle copy];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, seconds * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        if (nil != delayHandleCopy) {
            delayHandleCopy(NO);
        }
    });

    return delayHandleCopy;
}

static void cancel_delayed_block(CWDelayedBlockHandle delayedHandle)
{
    if (delayedHandle == nil) {
        return;
    }

    delayedHandle(YES);
}

# pragma mark - CWStatusBarNotification

@interface CWStatusBarNotification ()

@property (strong, nonatomic) UITapGestureRecognizer *tapGestureRecognizer;
@property (strong, nonatomic) CWDelayedBlockHandle dismissHandle;
@property (assign, nonatomic) BOOL isCustomView;
@property (nonatomic, strong) UIView *contentView;

@end

@implementation CWStatusBarNotification

- (instancetype)init {
    self = [super init];
    if (self) {
        // set default
        if ([[[UIApplication sharedApplication] delegate] respondsToSelector:@selector(window)]) {
            self.notificationLabelBackgroundColor = [[UIApplication sharedApplication] delegate].window.tintColor;
        } else {
            self.notificationLabelBackgroundColor = [UIColor blackColor];
        }
        self.notificationLabelTextColor = [UIColor whiteColor];
        self.notificationLabelFont = [UIFont systemFontOfSize:FONT_SIZE];
        self.notificationLabelHeight = 0.0;
        self.customView = nil;
        self.multiline = NO;
        self.supportedInterfaceOrientations = UIInterfaceOrientationMaskAllButUpsideDown;
        self.notificationAnimationDuration = 0.25;
        self.notificationStyle = CWNotificationStyleStatusBarNotification;
        self.notificationAnimationInStyle = CWNotificationAnimationStyleBottom;
        self.notificationAnimationOutStyle = CWNotificationAnimationStyleBottom;
        self.notificationAnimationType = CWNotificationAnimationTypeReplace;
        self.notificationIsDismissing = NO;
        self.isCustomView = NO;
        self.preferredStatusBarStyle = UIStatusBarStyleDefault;

        // create tap recognizer
        self.tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(notificationTapped:)];
        self.tapGestureRecognizer.numberOfTapsRequired = 1;

        // create default tap block
        __weak typeof(self) weakSelf = self;
        self.notificationTappedBlock = ^(void) {
            if (!weakSelf.notificationIsDismissing) {
                [weakSelf dismissNotification];
            }
        };
    }
    return self;
}

# pragma mark - dimensions

- (CGFloat)m_getStatusBarHeight
{
    if (self.notificationLabelHeight > 0) {
        return self.notificationLabelHeight;
    }

    CGFloat top = [self m_getTopAreaInset];
    if (top > 20) {
        return 30; // 刘海屏
    } else {
        CGFloat statusBarHeight = CGRectGetHeight([[UIApplication sharedApplication] statusBarFrame]);
        return MAX(statusBarHeight, 20);
    }
}

- (CGFloat)m_getTopAreaInset {
    if (@available(iOS 11.0, *)) {
        UIWindow *win = [[UIApplication sharedApplication].delegate window];
        if (win) {
            return win.safeAreaInsets.top;
        } else {
            return [UIApplication sharedApplication].keyWindow.safeAreaInsets.top;
        }
    }
    return 0.0f;
}

- (CGFloat)m_getTopOffset {
    CGFloat top = [self m_getTopAreaInset];
    if (top > 20) {
        if (self.notificationStyle != CWNotificationStyleNavigationBarNotification) {
            top -= 12;
        }
        return top;
    } else {
        return 0;
    }
}

- (CGFloat)m_getNavBarHeight {
    return 44;
}

- (CGFloat)m_getContentHeight {
    if (self.notificationStyle == CWNotificationStyleNavigationBarNotification) {
        return [self m_getNavBarHeight];
    } else {
        return [self m_getStatusBarHeight];
    }
}

- (CGFloat)m_getContainerViewWidth {
    return CGRectGetWidth([UIScreen mainScreen].bounds);
}

- (CGFloat)m_getTotalContainerViewHeight {
    return [self m_getTopOffset] + [self m_getContentHeight];
}

- (CGRect)m_getNotificationLabelFrame
{
    return CGRectMake(0, 0, [self m_getContainerViewWidth], [self m_getTotalContainerViewHeight]);
}

#pragma mark -

- (CGRect)getNotificationLabelLeftFrame
{
    return CGRectMake(-[self m_getContainerViewWidth], 0, [self m_getContainerViewWidth], [self m_getTotalContainerViewHeight]);
}

- (CGRect)getNotificationLabelRightFrame
{
    return CGRectMake([self m_getContainerViewWidth], 0, [self m_getContainerViewWidth], [self m_getTotalContainerViewHeight]);
}

- (CGRect)getNotificationLabelTopFrame
{
    return CGRectMake(0, -[self m_getTotalContainerViewHeight], [self m_getContainerViewWidth], [self m_getTotalContainerViewHeight]);
}

- (CGRect)getNotificationLabelBottomFrame
{
    return CGRectMake(0, [self m_getTotalContainerViewHeight], [self m_getContainerViewWidth], 0);
}

# pragma mark - screen orientation change

- (void)updateStatusBarFrame
{
    UIView *view = self.isCustomView ? self.customView : self.notificationLabel.superview;
    view.frame = [self m_getNotificationLabelFrame];
}

# pragma mark - on tap

- (void)notificationTapped:(UITapGestureRecognizer *)recognizer
{
    [self.notificationTappedBlock invoke];
}

# pragma mark - display helpers

- (void)setupNotificationView:(UIView *)view
{
    view.clipsToBounds = YES;
    view.userInteractionEnabled = YES;
    [view addGestureRecognizer:self.tapGestureRecognizer];
    switch (self.notificationAnimationInStyle) {
        case CWNotificationAnimationStyleTop:
            view.frame = [self getNotificationLabelTopFrame];
            break;
        case CWNotificationAnimationStyleBottom:
            view.frame = [self getNotificationLabelBottomFrame];
            break;
        case CWNotificationAnimationStyleLeft:
            view.frame = [self getNotificationLabelLeftFrame];
            break;
        case CWNotificationAnimationStyleRight:
            view.frame = [self getNotificationLabelRightFrame];
            break;
    }
}

- (void)createNotificationLabelWithMessage:(NSString *)message
{
    UIView *contentView = [UIView new];
    contentView.backgroundColor = self.notificationLabelBackgroundColor;
    self.contentView = contentView;

    self.notificationLabel = [ScrollLabel new];
    self.notificationLabel.numberOfLines = self.multiline ? 0 : 1;
    self.notificationLabel.text = message;
    self.notificationLabel.textAlignment = NSTextAlignmentCenter;
    self.notificationLabel.adjustsFontSizeToFitWidth = NO;
    self.notificationLabel.font = self.notificationLabelFont;
    self.notificationLabel.textColor = self.notificationLabelTextColor;
    self.notificationLabel.translatesAutoresizingMaskIntoConstraints = NO;
    [contentView addSubview:self.notificationLabel];

    CGFloat height = [self m_getContentHeight];

    [NSLayoutConstraint constraintWithItem:self.notificationLabel attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeLeftMargin multiplier:1.0 constant:0.0].active = YES;
    [NSLayoutConstraint constraintWithItem:self.notificationLabel attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeRightMargin multiplier:1.0 constant:0.0].active = YES;
    [NSLayoutConstraint constraintWithItem:self.notificationLabel attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:contentView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0].active = YES;
    [NSLayoutConstraint constraintWithItem:self.notificationLabel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:height].active = YES;

    [self setupNotificationView:contentView];
}

- (void)createNotificationCustomView:(UIView *)view
{
    self.customView = [[UIView alloc] init];
    self.customView.backgroundColor = self.notificationLabelBackgroundColor;
    [view setTranslatesAutoresizingMaskIntoConstraints:NO];
    [self.customView addSubview:view];

    CGFloat height = [self m_getContentHeight];

    [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.customView attribute:NSLayoutAttributeLeftMargin multiplier:1.0 constant:0.0].active = YES;
    [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.customView attribute:NSLayoutAttributeRightMargin multiplier:1.0 constant:0.0].active = YES;
    [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.customView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0.0].active = YES;
    [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:height].active = YES;

    [self setupNotificationView:self.customView];
}

- (void)createNotificationWindow
{
    self.notificationWindow = [[CWWindowContainer alloc] initWithFrame:[[[UIApplication sharedApplication] keyWindow] bounds]];
    self.notificationWindow.backgroundColor = [UIColor clearColor];
    self.notificationWindow.userInteractionEnabled = YES;
    self.notificationWindow.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    self.notificationWindow.windowLevel = UIWindowLevelStatusBar;

    CWViewController *rootVC = [[CWViewController alloc] init];
    [rootVC updateSupportedInterfaceOrientations:self.supportedInterfaceOrientations];
    [rootVC updateStatusBarStyle:self.preferredStatusBarStyle];

    self.notificationWindow.rootViewController = rootVC;
    self.notificationWindow.notificationHeight = [self m_getTotalContainerViewHeight];
}

# pragma mark - frame changing

- (void)firstFrameChange
{
    UIView *view = self.isCustomView ? self.customView : self.contentView;
    view.frame = [self m_getNotificationLabelFrame];
}

- (void)thirdFrameChange
{
    UIView *view = self.isCustomView ? self.customView : self.contentView;

    switch (self.notificationAnimationOutStyle) {
        case CWNotificationAnimationStyleTop:
            view.frame = [self getNotificationLabelTopFrame];
            break;
        case CWNotificationAnimationStyleBottom:
            view.frame = [self getNotificationLabelBottomFrame];
            break;
        case CWNotificationAnimationStyleLeft:
            view.frame = [self getNotificationLabelLeftFrame];
            break;
        case CWNotificationAnimationStyleRight:
            view.frame = [self getNotificationLabelRightFrame];
            break;
    }
}

# pragma mark - display notification

- (void)displayNotificationWithMessage:(NSString *)message completion:(void (^)(void))completion
{
    if (!self.notificationIsShowing) {
        self.isCustomView = NO;
        self.notificationIsShowing = YES;

        // create UIWindow
        [self createNotificationWindow];

        // create ScrollLabel
        [self createNotificationLabelWithMessage:message];

        // add label to window
        [self.notificationWindow.rootViewController.view addSubview:self.contentView];
        [self.notificationWindow.rootViewController.view bringSubviewToFront:self.contentView];
        [self.notificationWindow setHidden:NO];

        // checking for screen orientation change
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateStatusBarFrame) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];

        // checking for status bar change
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateStatusBarFrame) name:UIApplicationWillChangeStatusBarFrameNotification object:nil];

        // animate
        [UIView animateWithDuration:self.notificationAnimationDuration animations:^{
            [self firstFrameChange];
        } completion:^(BOOL finished) {
            double delayInSeconds = [self.notificationLabel scrollTime];
            perform_block_after_delay(delayInSeconds, ^{
                                          [completion invoke];
                                      });
        }];
    }
}

- (void)displayNotificationWithMessage:(NSString *)message forDuration:(NSTimeInterval)duration
{
    [self displayNotificationWithMessage:message completion:^{
        self.dismissHandle = perform_block_after_delay(duration, ^{
                                                           [self dismissNotification];
                                                       });
    }];
}

- (void)displayNotificationWithAttributedString:(NSAttributedString *)attributedString completion:(void (^)(void))completion
{
    [self displayNotificationWithMessage:[attributedString string] completion:completion];
    [[self notificationLabel] setAttributedText:attributedString];
}

- (void)displayNotificationWithAttributedString:(NSAttributedString *)attributedString forDuration:(NSTimeInterval)duration
{
    [self displayNotificationWithMessage:[attributedString string] forDuration:duration];
    [[self notificationLabel] setAttributedText:attributedString];
}

- (void)displayNotificationWithView:(UIView *)view completion:(void (^)(void))completion
{
    if (!self.notificationIsShowing) {
        self.isCustomView = YES;
        self.notificationIsShowing = YES;

        // create window
        [self createNotificationWindow];

        // setup view
        [self createNotificationCustomView:view];

        // add view to window
        UIView *rootView = self.notificationWindow.rootViewController.view;
        [rootView addSubview:self.customView];
        [rootView bringSubviewToFront:self.customView];
        [self.notificationWindow setHidden:NO];

        // checking for screen orientation change
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateStatusBarFrame) name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];

        // checking for status bar change
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateStatusBarFrame) name:UIApplicationWillChangeStatusBarFrameNotification object:nil];

        // animate
        [UIView animateWithDuration:self.notificationAnimationDuration animations:^{
            [self firstFrameChange];
        } completion:^(BOOL finished) {
            [completion invoke];
        }];
    }
}

- (void)displayNotificationWithView:(UIView *)view forDuration:(NSTimeInterval)duration
{
    [self displayNotificationWithView:view completion:^{
        self.dismissHandle = perform_block_after_delay(duration, ^{
                                                           [self dismissNotification];
                                                       });
    }];
}

- (void)dismissNotificationWithCompletion:(void (^)(void))completion
{
    if (self.notificationIsShowing) {
        cancel_delayed_block(self.dismissHandle);
        self.notificationIsDismissing = YES;

        [UIView animateWithDuration:self.notificationAnimationDuration animations:^{
            [self thirdFrameChange];
        } completion:^(BOOL finished) {
            UIView *view = self.isCustomView ? self.customView : self.contentView;
            [view removeFromSuperview];
            [self.notificationWindow setHidden:YES];
            self.notificationWindow = nil;
            view = nil;
            self.notificationIsShowing = NO;
            self.notificationIsDismissing = NO;
            [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidChangeStatusBarOrientationNotification object:nil];
            [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillChangeStatusBarFrameNotification object:nil];
            if (completion) {
                completion();
            }
        }];
    } else {
        if (completion) {
            completion();
        }
    }
}

- (void)dismissNotification
{
    [self dismissNotificationWithCompletion:nil];
}

@end
